#![allow(dead_code)]
#![allow(unused_imports)]

use std::collections::HashMap;

pub struct HashMapLrn {}

impl HashMapLrn {
    pub fn hashmap_lrn() {
        println!("HashMap Learning!");
    }
}

#[cfg(test)]
mod tests {
    use stdext::function_name;

    use super::*;

    #[test]
    /// new(), insert(), remove(), get(), len(),
    fn basic_test() {
        // 根据下文推测类型
        let mut map = HashMap::new();
        map.insert(1, "a");
        map.insert(2, "b");
        assert_eq!(map.get(&1), Some(&"a"));
        assert_eq!(map.get(&2), Some(&"b"));
        map.remove(&2);
        assert_eq!(map.get(&2), None);
        assert_eq!(map.len(), 1);
    }

    #[test]
    /// 尽可能的缩小容量
    fn shrink_to_test() {
        let mut map: HashMap<i32, i32> = HashMap::with_capacity(100);
        map.insert(1, 2);
        map.insert(3, 4);
        assert!(map.capacity() >= 100);
        map.shrink_to(10);
        assert!(map.capacity() >= 10);
        map.shrink_to(0);
        assert!(map.capacity() >= 2);
    }

    #[test]
    fn iter_test() {
        let map = HashMap::from([("a", 1), ("b", 2), ("c", 3)]);
        for (k, v) in &map {
            println!("{}:{}", k, v);
        }
        // 按任意顺序访问所有键值
        for (key, val) in map.iter() {
            println!("{}:{}", key, val);
        }
    }

    #[test]
    fn get_test() {
        let map = HashMap::from([("a", 1), ("b", 2), ("c", 3)]);
        assert_eq!(map.get(&"a"), Some(&1));
        assert_eq!(map.get_key_value(&"a"), Some((&"a", &1)));
    }

    #[test]
    fn get_mut_test() {
        let mut map = HashMap::from([("a", 1), ("b", 2), ("c", 3)]);
        if let Some(x) = map.get_mut(&"a") {
            *x = 10;
        }
        assert_eq!(map[&"a"], 10);
    }

    #[test]
    /// NOTE: 在map中获取给定键的对应项，以进行就地操作
    /// 键为空，就插入。键存在，就修改。
    fn entry_test() {
        let mut letters = HashMap::new();
        for ch in "a short treatise on fungi".chars() {
            letters
                .entry(ch)
                .and_modify(|counter| *counter += 1)
                .or_insert(1);
        }

        assert_eq!(letters[&'s'], 2);
        assert_eq!(letters[&'t'], 3);
        assert_eq!(letters[&'u'], 1);
        assert_eq!(letters[&'r'], 2);
        assert_eq!(letters.get(&'y'), None);
    }

    #[test]
    /// NOTE: 闭包用的是FnOnce(&mut V), 所以要用*解引用
    /// 在任何潜在的插入map之前，提供对占用条目的就地可变访问。
    ///
    /// - 使用闭包改变值
    fn and_modify_test() {
        let mut map = HashMap::new();
        map.entry("poneyland").and_modify(|e| *e += 1).or_insert(42);
        assert_eq!(map["poneyland"], 42);
        map.entry("poneyland").and_modify(|e| *e += 1).or_insert(42);
        assert_eq!(map["poneyland"], 43);
        map.entry("poneyland").and_modify(|e| *e = 10);
        assert_eq!(map["poneyland"], 10);
    }

    #[test]
    fn function_pointer_test() {
        type FnType = Box<dyn Fn(i32, i32) -> i32>;
        let mut map: HashMap<&str, FnType> = HashMap::new();
        map.insert("add", Box::new(add));
        map.entry("add")
            .and_modify(|x| *x = Box::new(sub))
            .or_insert(Box::new(add));
        map.entry("sub").or_insert(Box::new(sub));
        println!("function name: {}", function_name!());
        assert_eq!(map["add"](1, 2), -1);
        assert_eq!(map["sub"](1, 2), -1);

        fn add(a: i32, b: i32) -> i32 {
            a + b
        }

        fn sub(a: i32, b: i32) -> i32 {
            a - b
        }
    }
}
